"""
A double-ended queue, or deque, implemented using a list is a linear data structure that allows insertion and removal of elements from both ends. It supports operations such as adding or removing elements from the front or back of the deque.

Properties:
- Elements in a double-ended queue can be accessed and modified from both ends.
- The size of the deque can dynamically change as elements are added or removed.
- The order of elements in the deque is preserved.

Operations:

1. Initialization:
   - `deque()`: Creates an empty double-ended queue.

2. Basic Operations:
   - `append_left(item)`: Adds an element to the front of the deque.
   - `append_right(item)`: Adds an element to the back of the deque.
   - `pop_left()`: Removes and returns the element from the front of the deque.
   - `pop_right()`: Removes and returns the element from the back of the deque.
   - `peek_left()`: Returns the element at the front of the deque without removing it.
   - `peek_right()`: Returns the element at the back of the deque without removing it.
   - `is_empty()`: Checks if the deque is empty.
   - `size()`: Returns the number of elements in the deque.

Example Usage:
```
deque = []

deque.append_left(3)  # deque: [3]
deque.append_right(7)  # deque: [3, 7]
deque.append_left(1)  # deque: [1, 3, 7]

print(deque.pop_right())  # Output: 7
print(deque.peek_left())  # Output: 1
print(deque.size())  # Output: 2
print(deque.is_empty())  # Output: False
```

Time and Space Complexity:
- Appending or popping elements at either end of the deque takes constant time, O(1).
- Peeking at elements without removal also has a time complexity of O(1).
- Checking if the deque is empty or obtaining its size can be done in constant time.
- The space complexity of a deque implemented using a list is linear, O(n), where n is the number of elements in the deque.
"""

class Deque:
    """
    A simple implementation of a deque.

    This deque class supports insertion and deletion at both ends,
    as well as checking the size, and determining if the deque is empty or full.

    Attributes:
        capacity (int): The maximum capacity of the deque.
    """

    def __init__(self, capacity: int = 10):
        """
        Initialize the deque.

        Args:
            capacity (int, optional): The maximum capacity of the deque. Defaults to 10.
        """
        self._data = []
        self._capacity = capacity

    def __len__(self) -> int:
        """Return the number of elements in the deque."""
        return len(self._data)

    def __repr__(self):
        """Return a string representation of the deque."""
        return f'Deque(capacity={self._capacity}, data={self._data})'

    def __str__(self):
        """Return a formatted string representation of the deque."""
        return str(self._data)

    def is_empty(self) -> bool:
        """Check if the deque is empty."""
        return len(self._data) == 0

    def is_full(self) -> bool:
        """Check if the deque is full."""
        return len(self._data) == self._capacity

    def push_front(self, item: object):
        """Insert an item at the front of the deque."""
        if self.is_full():
            raise IndexError('Deque is full')
        self._data.insert(0, item)

    def push_back(self, item: object):
        """Insert an item at the back of the deque."""
        if self.is_full():
            raise IndexError('Deque is full')
        self._data.append(item)

    def pop_front(self) -> object:
        """Remove and return the item at the front of the deque."""
        if self.is_empty():
            raise IndexError('Deque is empty')
        return self._data.pop(0)

    def pop_back(self) -> object:
        """Remove and return the item at the back of the deque."""
        if self.is_empty():
            raise IndexError('Deque is empty')
        return self._data.pop()

    def get_front(self) -> object:
        """Return the item at the front of the deque without removing it."""
        if self.is_empty():
            raise IndexError('Deque is empty')
        return self._data[0]

    def get_back(self) -> object:
        """Return the item at the back of the deque without removing it."""
        if self.is_empty():
            raise IndexError('Deque is empty')
        return self._data[-1]

    def size(self) -> int:
        """Return the current size of the deque."""
        return len(self._data)

    def get_capacity(self) -> int:
        """Return the maximum capacity of the deque."""
        return self._capacity


if __name__ == "__main__":
    deque = Deque(5)  # Initialize a deque with capacity 5

    # Insert elements into the deque
    deque.push_back(3)
    deque.push_front(5)
    deque.push_back(6)

    print("Deque:", deque)  # Expected output: Deque: [5, 3, 6]
    print("Deque size:", len(deque))  # Expected output: Deque size: 3
    print("Front item of Deque:", deque.get_front())  # Expected output: Front item of Deque: 5
    print("Back item of Deque:", deque.get_back())  # Expected output: Back item of Deque: 6

    # Remove elements from the deque
    item = deque.pop_front()  # item = 5
    print("Removed item from front of Deque:", item)  # Expected output: Removed item from front of Deque: 5

    item = deque.pop_back()  # item = 6
    print("Removed item from back of Deque:", item)  # Expected output: Removed item from back of Deque: 6

    print("Deque:", deque)  # Expected output: Deque: [3]
    print("Is Deque empty?", deque.is_empty())  # Expected output: Is Deque empty? False

    # Clear the deque
    deque.clear()
    print("Deque after clear:", deque)  # Expected output: Deque after clear: []
    print("Deque size:", len(deque))  # Expected output: Deque size: 0

    # Check the current size and capacity of the deque
    print("Current size of Deque:", deque.size())  # Expected output: Current size of Deque: 0
    print("Maximum capacity of Deque:", deque.get_capacity())  # Expected output: Maximum capacity of Deque: 5
